agora inbox for [email protected]  
help / color / mirror / Atom feed
[PATCH v4 3/3] Adding the pg_stat_backend_transaction view
407+ messages / 2 participants
[nested] [flat]

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v4 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  57 +++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 223 insertions(+)
  51.7% doc/src/sgml/
   3.2% src/backend/catalog/
  23.1% src/backend/utils/adt/
   7.0% src/include/catalog/
   9.6% src/test/regress/expected/
   5.1% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..025641c2f70 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1b3c5a55882..ee3f653f360 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..fa92099e789 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 118d6da1ace..24ec3d861a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5641,6 +5641,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..ae376f96998 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 605f5070376..7079198bc06 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 54e72866344..0d6a408c7b5 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--jg9kqX3osLGO+8A7--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v5 3/3] Adding the pg_stat_backend_transaction view
@ 2025-08-09 14:22  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw)

This view displays one row per server process, showing transaction statistics
related to the current activity of that process. It currently displays the pid,
the number of XIDs generated, the number of commits, the number of rollbacks and
the time at which these statistics were last reset.

It's built on top of a new function (pg_stat_get_backend_transactions()). The idea
is the same as pg_stat_activity and pg_stat_get_activity().

Adding documentation and tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml         | 115 +++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   9 +++
 src/backend/utils/adt/pgstatfuncs.c  |  65 +++++++++++++++
 src/include/catalog/pg_proc.dat      |   9 +++
 src/test/regress/expected/rules.out  |   6 ++
 src/test/regress/expected/stats.out  |  17 ++++
 src/test/regress/sql/stats.sql       |  10 +++
 7 files changed, 231 insertions(+)
  49.8% doc/src/sgml/
   3.1% src/backend/catalog/
  25.8% src/backend/utils/adt/
   6.8% src/include/catalog/
   9.2% src/test/regress/expected/
   4.9% src/test/regress/sql/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1069b..86200553293 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <structname>pg_stat_backend_transaction</structname>
+       <indexterm><primary>pg_stat_backend_transaction</primary></indexterm>
+      </entry>
+      <entry>
+       One row per server process, showing statistics related to
+       the current activity of that process, such as number of commits and
+       rollbacks.
+       See <link linkend="monitoring-pg-stat-backend-transaction-view">
+       <structname>pg_stat_backend_transaction</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry>
       <entry>One row per WAL sender process, showing statistics about
@@ -1195,6 +1209,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
    </note>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-backend-transaction-view">
+  <title><structname>pg_stat_backend_transaction</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_backend_transaction</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_backend_transaction</structname> view will have one row
+   per server process, showing statistics related to
+   the current activity of that process.
+  </para>
+
+  <table id="pg-stat-backend-transaction-view" xreflabel="pg_stat_backend_transaction">
+   <title><structname>pg_stat_backend_transaction</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>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of this backend
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xid_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of XID that have been generated by the backend. It does not take
+       into account virtual transaction ID on purpose.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_commit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been committed.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>xact_rollback</structfield> <type>bigint</type>
+      </para>
+      <para>
+       The number of transactions that have been rolled back.
+      </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>
+
+  <note>
+   <para>
+    The view does not return statistics for the checkpointer,
+    the background writer, the startup process and the autovacuum launcher.
+   </para>
+  </note>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-replication-view">
   <title><structname>pg_stat_replication</structname></title>
 
@@ -5342,6 +5441,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_transactions</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_transactions</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns a record of transaction statistics about the backend with the
+        specified process ID, or one record for each active backend in the system
+        if <literal>NULL</literal> is specified.  The fields returned are a
+        subset of those in the <structname>pg_stat_backend_transaction</structname> view.
+       </para></entry>
+      </row>
+
       <row>
        <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e54018004db..e005eaeb579 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -946,6 +946,15 @@ CREATE VIEW pg_stat_activity AS
         LEFT JOIN pg_database AS D ON (S.datid = D.oid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_backend_transaction AS
+    SELECT
+            S.pid,
+            S.xid_count,
+            S.xact_commit,
+            S.xact_rollback,
+            S.stats_reset
+    FROM pg_stat_get_backend_transactions(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e6b83..77ee9222ccf 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -708,6 +708,71 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns transactions statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS	5
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	InitMaterializedSRF(fcinfo, 0);
+
+	/* 1-based index */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		/* for each row */
+		Datum		values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		bool		nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		PgStat_Backend *backend_stats;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* If looking for specific PID, ignore all the others */
+		if (pid != -1 && beentry->st_procpid != pid)
+			continue;
+
+		/* check if the backend type tracks statistics */
+		if (!pgstat_tracks_backend_bktype(beentry->st_backendType))
+			continue;
+
+		/*
+		 * Don't use pgstat_fetch_stat_backend_by_pid() to avoid holding the
+		 * ProcArrayLock during each iteration.
+		 */
+		backend_stats = pgstat_fetch_stat_backend(local_beentry->proc_number);
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		if (!backend_stats)
+			continue;
+
+		values[1] = Int64GetDatum(backend_stats->xid_count);
+		values[2] = Int64GetDatum(backend_stats->xact_commit);
+		values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+		if (backend_stats->stat_reset_timestamp != 0)
+			values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+		else
+			nulls[4] = true;
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+		/* If only a single backend was requested, and we found it, break. */
+		if (pid != -1)
+			break;
+	}
+
+	return (Datum) 0;
+}
 
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..6a686e93fdd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5680,6 +5680,15 @@
   proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
   proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+  descr => 'statistics: statistics about currently active backends',
+  proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => 'int4',
+  proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+  prosrc => 'pg_stat_get_backend_transactions' },
 { oid => '6318', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
   proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2b3cf6d8569..2b4ea519677 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1860,6 +1860,12 @@ pg_stat_archiver| SELECT archived_count,
     last_failed_time,
     stats_reset
    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_transaction| SELECT pid,
+    xid_count,
+    xact_commit,
+    xact_rollback,
+    stats_reset
+   FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, 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 ea7f7846895..e3f55a4c0ea 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968c83e..e6b35593a95 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
 DELETE FROM trunc_stats_test1 WHERE id = 3;
 
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
 BEGIN;
 UPDATE trunc_stats_test1 SET id = id + 100;
 TRUNCATE trunc_stats_test1;
 INSERT INTO trunc_stats_test1 DEFAULT VALUES;
 COMMIT;
 
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+  FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
 -- use a savepoint: 1 insert, 1 live
 BEGIN;
 INSERT INTO trunc_stats_test2 DEFAULT VALUES;
-- 
2.34.1


--uN5gGi2rtCNSVbpC--





^ permalink  raw  reply  [nested|flat] 407+ messages in thread

* [PATCH v56 2/3] Reserve replication slots specifically for REPACK
@ 2026-04-01 17:54  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 407+ messages in thread

From: Álvaro Herrera @ 2026-04-01 17:54 UTC (permalink / raw)

This allows REPACK to not interfere with other operations that use
replication slots.  This eases configurability.
---
 doc/src/sgml/config.sgml                      | 16 ++++
 doc/src/sgml/ref/repack.sgml                  |  6 +-
 src/backend/commands/repack.c                 |  2 +-
 src/backend/commands/repack_worker.c          |  7 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/logical.c     |  8 +-
 .../replication/logical/logicalfuncs.c        |  2 +-
 src/backend/replication/logical/slotsync.c    |  5 +-
 src/backend/replication/slot.c                | 84 ++++++++++++-------
 src/backend/replication/slotfuncs.c           | 19 +++--
 src/backend/replication/walsender.c           |  9 +-
 src/backend/utils/misc/guc_parameters.dat     |  8 ++
 src/backend/utils/misc/postgresql.conf.sample |  2 +
 src/include/replication/logical.h             |  3 +-
 src/include/replication/slot.h                |  5 +-
 15 files changed, 116 insertions(+), 62 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 3324d2d3c49..3435732646e 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -4639,6 +4639,22 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"'  # Windows
        </listitem>
       </varlistentry>
 
+      <varlistentry id="guc-max-repack-replication-slots" xreflabel="max_repack_replication_slots">
+       <term><varname>max_repack_replication_slots</varname> (<type>integer</type>)
+       <indexterm>
+        <primary><varname>max_repack_replication_slots</varname> configuration parameter</primary>
+       </indexterm>
+       </term>
+       <listitem>
+        <para>
+         Specifies the maximum number of replication slots for use of
+         the <command>REPACK</command> command.  The default is 5.
+         This parameter can only be set at server start.
+        </para>
+       </listitem>
+      </varlistentry>
+
+
       <varlistentry id="guc-max-replication-slots" xreflabel="max_replication_slots">
        <term><varname>max_replication_slots</varname> (<type>integer</type>)
        <indexterm>
diff --git a/doc/src/sgml/ref/repack.sgml b/doc/src/sgml/ref/repack.sgml
index e993dfb3108..c532a39ee07 100644
--- a/doc/src/sgml/ref/repack.sgml
+++ b/doc/src/sgml/ref/repack.sgml
@@ -293,9 +293,9 @@ REPACK [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] USING
 
        <listitem>
         <para>
-         The <link linkend="guc-max-replication-slots"><varname>max_replication_slots</varname></link>
-         configuration parameter does not allow for creation of an additional
-         replication slot.
+         The <link linkend="guc-max-repack-replication-slots"><varname>max_repack_replication_slots</varname></link>
+         configuration parameter does not allow for the creation of an
+         additional replication slot.
         </para>
        </listitem>
       </itemizedlist>
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 17b639b3b44..5e97fcf3818 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -3357,7 +3357,7 @@ start_repack_decoding_worker(Oid relid)
 		ereport(ERROR,
 				errcode(ERRCODE_CONFIGURATION_LIMIT_EXCEEDED),
 				errmsg("out of background worker slots"),
-				errhint("You might need to increase \"%s\".", "max_worker_processes"));
+				errhint("You might need to increase \"%s\".", "max_repack_replication_slots"));
 
 	decoding_worker->seg = seg;
 	decoding_worker->error_mqh = mqh;
diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 94d034970b5..ea330310e27 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -212,7 +212,7 @@ repack_setup_logical_decoding(Oid relid)
 	 * Make sure we can use logical decoding.
 	 */
 	CheckSlotPermissions();
-	CheckLogicalDecodingRequirements();
+	CheckLogicalDecodingRequirements(true);
 
 	/*
 	 * A single backend should not execute multiple REPACK commands at a time,
@@ -221,8 +221,8 @@ repack_setup_logical_decoding(Oid relid)
 	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
 	 */
 	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
-	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
-						  false);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, true,
+						  false, false);
 
 	EnsureLogicalDecodingEnabled();
 
@@ -233,6 +233,7 @@ repack_setup_logical_decoding(Oid relid)
 	ctx = CreateInitDecodingContext(REPL_PLUGIN_NAME,
 									NIL,
 									true,
+									true,
 									InvalidXLogRecPtr,
 									XL_ROUTINE(.page_read = read_local_xlog_page,
 											   .segment_open = wal_segment_open,
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 9e75a3e04ee..7adf4dbe0d1 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1575,7 +1575,7 @@ CreateConflictDetectionSlot(void)
 			errmsg("creating replication conflict detection slot"));
 
 	ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	init_conflict_slot_xmin();
 }
diff --git a/src/backend/replication/logical/logical.c b/src/backend/replication/logical/logical.c
index 8ceaf64d164..d8e02c53558 100644
--- a/src/backend/replication/logical/logical.c
+++ b/src/backend/replication/logical/logical.c
@@ -108,9 +108,9 @@ static void LoadOutputPlugin(OutputPluginCallbacks *callbacks, const char *plugi
  * decoding.
  */
 void
-CheckLogicalDecodingRequirements(void)
+CheckLogicalDecodingRequirements(bool repack)
 {
-	CheckSlotRequirements();
+	CheckSlotRequirements(repack);
 
 	/*
 	 * NB: Adding a new requirement likely means that RestoreSlotFromDisk()
@@ -304,6 +304,7 @@ StartupDecodingContext(List *output_plugin_options,
  * output_plugin_options -- contains options passed to the output plugin
  * need_full_snapshot -- if true, must obtain a snapshot able to read all
  *		tables; if false, one that can read only catalogs is acceptable.
+ * for_repack -- if true, we're going to be decoding for REPACK.
  * restart_lsn -- if given as invalid, it's this routine's responsibility to
  *		mark WAL as reserved by setting a convenient restart_lsn for the slot.
  *		Otherwise, we set for decoding to start from the given LSN without
@@ -324,6 +325,7 @@ LogicalDecodingContext *
 CreateInitDecodingContext(const char *plugin,
 						  List *output_plugin_options,
 						  bool need_full_snapshot,
+						  bool for_repack,
 						  XLogRecPtr restart_lsn,
 						  XLogReaderRoutine *xl_routine,
 						  LogicalOutputPluginWriterPrepareWrite prepare_write,
@@ -340,7 +342,7 @@ CreateInitDecodingContext(const char *plugin,
 	 * On a standby, this check is also required while creating the slot.
 	 * Check the comments in the function.
 	 */
-	CheckLogicalDecodingRequirements();
+	CheckLogicalDecodingRequirements(for_repack);
 
 	/* shorter lines... */
 	slot = MyReplicationSlot;
diff --git a/src/backend/replication/logical/logicalfuncs.c b/src/backend/replication/logical/logicalfuncs.c
index 9760818941d..512013b0ef0 100644
--- a/src/backend/replication/logical/logicalfuncs.c
+++ b/src/backend/replication/logical/logicalfuncs.c
@@ -115,7 +115,7 @@ pg_logical_slot_get_changes_guts(FunctionCallInfo fcinfo, bool confirm, bool bin
 
 	CheckSlotPermissions();
 
-	CheckLogicalDecodingRequirements();
+	CheckLogicalDecodingRequirements(false);
 
 	if (PG_ARGISNULL(0))
 		ereport(ERROR,
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index 8b53bd3ac7f..ae900f13467 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -434,7 +434,7 @@ get_local_synced_slots(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < max_replication_slots + max_repack_replication_slots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -823,6 +823,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 		 */
 		ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
 							  remote_slot->two_phase,
+							  false,
 							  remote_slot->failover,
 							  true);
 
@@ -1707,7 +1708,7 @@ update_synced_slots_inactive_since(void)
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < max_replication_slots + max_repack_replication_slots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a1f37e59dbc..e6722fc0212 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -160,6 +160,8 @@ ReplicationSlot *MyReplicationSlot = NULL;
 /* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
+int			max_repack_replication_slots = 5;	/* the maximum number of slots
+												 * for REPACK */
 
 /*
  * Invalidate replication slots that have remained idle longer than this
@@ -199,12 +201,13 @@ ReplicationSlotsShmemRequest(void *arg)
 {
 	Size		size;
 
-	if (max_replication_slots == 0)
+	if (max_replication_slots + max_repack_replication_slots == 0)
 		return;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
-					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
+					mul_size(max_replication_slots + max_repack_replication_slots,
+							 sizeof(ReplicationSlot)));
 	ShmemRequestStruct(.name = "ReplicationSlot Ctl",
 					   .size = size,
 					   .ptr = (void **) &ReplicationSlotCtl,
@@ -217,7 +220,7 @@ ReplicationSlotsShmemRequest(void *arg)
 static void
 ReplicationSlotsShmemInit(void *arg)
 {
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < max_replication_slots + max_repack_replication_slots; i++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
@@ -366,6 +369,7 @@ IsSlotForConflictCheck(const char *name)
  * db_specific: logical decoding is db specific; if the slot is going to
  *	   be used for that pass true, otherwise false.
  * two_phase: If enabled, allows decoding of prepared transactions.
+ * repack: If true, use a slot from the pool for REPACK.
  * failover: If enabled, allows the slot to be synced to standbys so
  *     that logical replication can be resumed after failover.
  * synced: True if the slot is synchronized from the primary server.
@@ -373,10 +377,11 @@ IsSlotForConflictCheck(const char *name)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover, bool synced)
+					  bool two_phase, bool repack, bool failover, bool synced)
 {
 	ReplicationSlot *slot = NULL;
-	int			i;
+	int			startpoint,
+				endpoint;
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -425,12 +430,16 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
 
 	/*
-	 * Check for name collision, and identify an allocatable slot.  We need to
-	 * hold ReplicationSlotControlLock in shared mode for this, so that nobody
-	 * else can change the in_use flags while we're looking at them.
+	 * Check for name collision (across the whole array), and identify an
+	 * allocatable slot (in the array slice specific to our current use case:
+	 * either general, or REPACK only).  We need to hold
+	 * ReplicationSlotControlLock in shared mode for this, so that nobody else
+	 * can change the in_use flags while we're looking at them.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	startpoint = !repack ? 0 : max_replication_slots;
+	endpoint = max_replication_slots + (repack ? max_repack_replication_slots : 0);
+	for (int i = 0; i < max_replication_slots + max_repack_replication_slots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -438,7 +447,9 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("replication slot \"%s\" already exists", name)));
-		if (!s->in_use && slot == NULL)
+
+		if (i >= startpoint && i < endpoint &&
+			!s->in_use && slot == NULL)
 			slot = s;
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -448,7 +459,8 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 		ereport(ERROR,
 				(errcode(ERRCODE_CONFIGURATION_LIMIT_EXCEEDED),
 				 errmsg("all replication slots are in use"),
-				 errhint("Free one or increase \"max_replication_slots\".")));
+				 errhint("Free one or increase \"%s\".",
+						 repack ? "max_repack_replication_slots" : "max_replication_slots")));
 
 	/*
 	 * Since this slot is not in use, nobody should be looking at any part of
@@ -541,7 +553,7 @@ SearchNamedReplicationSlot(const char *name, bool need_lock)
 	if (need_lock)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < max_replication_slots + max_repack_replication_slots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -569,7 +581,8 @@ int
 ReplicationSlotIndex(ReplicationSlot *slot)
 {
 	Assert(slot >= ReplicationSlotCtl->replication_slots &&
-		   slot < ReplicationSlotCtl->replication_slots + max_replication_slots);
+		   slot < ReplicationSlotCtl->replication_slots +
+		   (max_replication_slots + max_repack_replication_slots));
 
 	return slot - ReplicationSlotCtl->replication_slots;
 }
@@ -863,7 +876,7 @@ ReplicationSlotCleanup(bool synced_only)
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < max_replication_slots + max_repack_replication_slots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1245,7 +1258,7 @@ ReplicationSlotsComputeRequiredXmin(bool already_locked)
 	if (!already_locked)
 		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < max_replication_slots + max_repack_replication_slots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		TransactionId effective_xmin;
@@ -1300,7 +1313,7 @@ ReplicationSlotsComputeRequiredLSN(void)
 	Assert(ReplicationSlotCtl != NULL);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < max_replication_slots + max_repack_replication_slots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		XLogRecPtr	restart_lsn;
@@ -1367,12 +1380,12 @@ ReplicationSlotsComputeLogicalRestartLSN(void)
 	XLogRecPtr	result = InvalidXLogRecPtr;
 	int			i;
 
-	if (max_replication_slots <= 0)
+	if (max_replication_slots + max_repack_replication_slots <= 0)
 		return InvalidXLogRecPtr;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < max_replication_slots + max_repack_replication_slots; i++)
 	{
 		ReplicationSlot *s;
 		XLogRecPtr	restart_lsn;
@@ -1447,11 +1460,11 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 
 	*nslots = *nactive = 0;
 
-	if (max_replication_slots <= 0)
+	if (max_replication_slots + max_repack_replication_slots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < max_replication_slots + max_repack_replication_slots; i++)
 	{
 		ReplicationSlot *s;
 
@@ -1508,13 +1521,13 @@ ReplicationSlotsDropDBSlots(Oid dboid)
 	bool		found_valid_logicalslot;
 	bool		dropped = false;
 
-	if (max_replication_slots <= 0)
+	if (max_replication_slots + max_repack_replication_slots <= 0)
 		return;
 
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < max_replication_slots + max_repack_replication_slots; i++)
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
@@ -1611,11 +1624,11 @@ CheckLogicalSlotExists(void)
 {
 	bool		found = false;
 
-	if (max_replication_slots <= 0)
+	if (max_replication_slots + max_repack_replication_slots <= 0)
 		return false;
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < max_replication_slots + max_repack_replication_slots; i++)
 	{
 		ReplicationSlot *s;
 		bool		invalidated;
@@ -1649,17 +1662,24 @@ CheckLogicalSlotExists(void)
  * slots.
  */
 void
-CheckSlotRequirements(void)
+CheckSlotRequirements(bool repack)
 {
 	/*
 	 * NB: Adding a new requirement likely means that RestoreSlotFromDisk()
 	 * needs the same check.
 	 */
 
-	if (max_replication_slots == 0)
+	if (!repack && max_replication_slots == 0)
 		ereport(ERROR,
-				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
+				errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				errmsg("replication slots can only be used if \"%s\" > 0",
+					   "max_replication_slots"));
+
+	if (repack && max_repack_replication_slots == 0)
+		ereport(ERROR,
+				errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				errmsg("REPACK can only be used if \"%s\" > 0",
+					   "max_repack_replication_slots"));
 
 	if (wal_level < WAL_LEVEL_REPLICA)
 		ereport(ERROR,
@@ -2210,7 +2230,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 	Assert(!(possible_causes & RS_INVAL_WAL_REMOVED) || oldestSegno > 0);
 	Assert(possible_causes != RS_INVAL_NONE);
 
-	if (max_replication_slots == 0)
+	if (max_replication_slots == 0 && max_repack_replication_slots == 0)
 		return invalidated;
 
 	XLogSegNoOffsetToRecPtr(oldestSegno, 0, wal_segment_size, oldestLSN);
@@ -2218,7 +2238,7 @@ InvalidateObsoleteReplicationSlots(uint32 possible_causes,
 restart:
 	found_valid_logicalslot = false;
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < max_replication_slots + max_repack_replication_slots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		bool		released_lock = false;
@@ -2323,7 +2343,7 @@ CheckPointReplicationSlots(bool is_shutdown)
 	 */
 	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);
 
-	for (i = 0; i < max_replication_slots; i++)
+	for (i = 0; i < max_replication_slots + max_repack_replication_slots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 		char		path[MAXPGPATH];
@@ -2424,7 +2444,7 @@ StartupReplicationSlots(void)
 	FreeDir(replication_dir);
 
 	/* currently no slots exist, we're done. */
-	if (max_replication_slots <= 0)
+	if (max_replication_slots + max_repack_replication_slots <= 0)
 		return;
 
 	/* Now that we have recovered all the data, compute replication xmin */
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 9f5e4f998fe..16fbd383735 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -53,7 +53,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false, false);
+						  false, false, false);
 
 	if (immediately_reserve)
 	{
@@ -90,7 +90,7 @@ pg_create_physical_replication_slot(PG_FUNCTION_ARGS)
 
 	CheckSlotPermissions();
 
-	CheckSlotRequirements();
+	CheckSlotRequirements(false);
 
 	create_physical_replication_slot(NameStr(*name),
 									 immediately_reserve,
@@ -146,7 +146,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover, false);
+						  false, failover, false);
 
 	/*
 	 * Ensure the logical decoding is enabled before initializing the logical
@@ -164,6 +164,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ctx = CreateInitDecodingContext(plugin, NIL,
 									false,	/* just catalogs is OK */
+									false,	/* not repack */
 									restart_lsn,
 									XL_ROUTINE(.page_read = read_local_xlog_page,
 											   .segment_open = wal_segment_open,
@@ -203,7 +204,7 @@ pg_create_logical_replication_slot(PG_FUNCTION_ARGS)
 
 	CheckSlotPermissions();
 
-	CheckLogicalDecodingRequirements();
+	CheckLogicalDecodingRequirements(false);
 
 	create_logical_replication_slot(NameStr(*name),
 									NameStr(*plugin),
@@ -240,7 +241,7 @@ pg_drop_replication_slot(PG_FUNCTION_ARGS)
 
 	CheckSlotPermissions();
 
-	CheckSlotRequirements();
+	CheckSlotRequirements(false);
 
 	ReplicationSlotDrop(NameStr(*name), true);
 
@@ -270,7 +271,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	currlsn = GetXLogWriteRecPtr();
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
-	for (slotno = 0; slotno < max_replication_slots; slotno++)
+	for (slotno = 0; slotno < max_replication_slots + max_repack_replication_slots; slotno++)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
@@ -648,9 +649,9 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	CheckSlotPermissions();
 
 	if (logical_slot)
-		CheckLogicalDecodingRequirements();
+		CheckLogicalDecodingRequirements(false);
 	else
-		CheckSlotRequirements();
+		CheckSlotRequirements(false);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
@@ -665,7 +666,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	 * managed to create the new slot, we advance the new slot's restart_lsn
 	 * to the source slot's updated restart_lsn the second time we lock it.
 	 */
-	for (int i = 0; i < max_replication_slots; i++)
+	for (int i = 0; i < max_replication_slots + max_repack_replication_slots; i++)
 	{
 		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index b4a2117a7f9..bad45adb004 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1241,7 +1241,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false, false);
+							  false, false, false, false);
 
 		if (reserve_wal)
 		{
@@ -1261,7 +1261,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 
 		Assert(cmd->kind == REPLICATION_KIND_LOGICAL);
 
-		CheckLogicalDecodingRequirements();
+		CheckLogicalDecodingRequirements(false);
 
 		/*
 		 * Initially create persistent slot as ephemeral - that allows us to
@@ -1272,7 +1272,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover, false);
+							  two_phase, false, failover, false);
 
 		/*
 		 * Do options check early so that we can bail before calling the
@@ -1330,6 +1330,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		Assert(IsLogicalDecodingEnabled());
 
 		ctx = CreateInitDecodingContext(cmd->plugin, NIL, need_full_snapshot,
+										false,
 										InvalidXLogRecPtr,
 										XL_ROUTINE(.page_read = logical_read_xlog_page,
 												   .segment_open = WalSndSegmentOpen,
@@ -1487,7 +1488,7 @@ StartLogicalReplication(StartReplicationCmd *cmd)
 	QueryCompletion qc;
 
 	/* make sure that our requirements are still fulfilled */
-	CheckLogicalDecodingRequirements();
+	CheckLogicalDecodingRequirements(false);
 
 	Assert(!MyReplicationSlot);
 
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index fcb6ab80583..632f3ba4989 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2079,6 +2079,14 @@
   max => 'MAX_BACKENDS',
 },
 
+{ name => 'max_repack_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
+  short_desc => 'Sets the maximum number of replication slots for use by REPACK.',
+  variable => 'max_repack_replication_slots',
+  boot_val => '5',
+  min => '0',
+  max => 'MAX_BACKENDS',
+},
+
 /* see max_wal_senders */
 { name => 'max_replication_slots', type => 'int', context => 'PGC_POSTMASTER', group => 'REPLICATION_SENDING',
   short_desc => 'Sets the maximum number of simultaneously defined replication slots.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index e3e462f3efb..2e10eb4a36a 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -348,6 +348,8 @@
                                 # (change requires restart)
 #max_replication_slots = 10     # max number of replication slots
                                 # (change requires restart)
+#max_repack_replication_slots = 5 # max number of replication slots for REPACK
+                                # (change requires restart)
 #wal_keep_size = 0              # in megabytes; 0 disables
 #max_slot_wal_keep_size = -1    # in megabytes; -1 disables
 #idle_replication_slot_timeout = 0      # in seconds; 0 disables
diff --git a/src/include/replication/logical.h b/src/include/replication/logical.h
index bc9d4ece672..bc075b16741 100644
--- a/src/include/replication/logical.h
+++ b/src/include/replication/logical.h
@@ -115,11 +115,12 @@ typedef struct LogicalDecodingContext
 } LogicalDecodingContext;
 
 
-extern void CheckLogicalDecodingRequirements(void);
+extern void CheckLogicalDecodingRequirements(bool repack);
 
 extern LogicalDecodingContext *CreateInitDecodingContext(const char *plugin,
 														 List *output_plugin_options,
 														 bool need_full_snapshot,
+														 bool for_repack,
 														 XLogRecPtr restart_lsn,
 														 XLogReaderRoutine *xl_routine,
 														 LogicalOutputPluginWriterPrepareWrite prepare_write,
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 1a3557de607..77c8d0975b6 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -324,13 +324,14 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT int max_repack_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover,
+								  bool two_phase, bool repack, bool failover,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
@@ -373,7 +374,7 @@ extern void ReplicationSlotDropAtPubNode(WalReceiverConn *wrconn, char *slotname
 extern void StartupReplicationSlots(void);
 extern void CheckPointReplicationSlots(bool is_shutdown);
 
-extern void CheckSlotRequirements(void);
+extern void CheckSlotRequirements(bool repack);
 extern void CheckSlotPermissions(void);
 extern ReplicationSlotInvalidationCause
 			GetSlotInvalidationCause(const char *cause_name);
-- 
2.47.3


--qs77q6fpwolne4ds
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v56-0003-Error-out-any-process-that-would-block-at-REPACK.patch"



^ permalink  raw  reply  [nested|flat] 407+ messages in thread


end of thread, other threads:[~2026-04-01 17:54 UTC | newest]

Thread overview: 407+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v5 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2025-08-09 14:22 [PATCH v4 3/3] Adding the pg_stat_backend_transaction view Bertrand Drouvot <[email protected]>
2026-04-01 17:54 [PATCH v56 2/3] Reserve replication slots specifically for REPACK Álvaro Herrera <[email protected]>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox