agora inbox for [email protected]  
help / color / mirror / Atom feed
[PATCH v2] Avoid orphaned objects dependencies
141+ messages / 1 participants
[nested] [flat]

* [PATCH v2] Avoid orphaned objects dependencies
@ 2024-03-29 15:43 Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 141+ messages in thread

From: Bertrand Drouvot @ 2024-03-29 15:43 UTC (permalink / raw)

It's currently possible to create orphaned objects dependencies, for example:

Scenario 1:

session 1: begin; drop schema schem;
session 2: create a function in the schema schem
session 1: commit;

With the above, the function created in session 2 would be linked to a non
existing schema.

Scenario 2:

session 1: begin; create a function in the schema schem
session 2: drop schema schem;
session 1: commit;

With the above, the function created in session 1 would be linked to a non
existing schema.

To avoid those scenarios, a new lock (that conflicts with a lock taken by DROP)
has been put in place when the dependencies are being recorded. With this in
place, the drop schema in scenario 2 would be locked.

Also, after locking the object, the patch checks that the object still exists:
with this in place session 2 in scenario 1 would be locked and would report an
error once session 1 committs (that would not be the case should session 1 abort
the transaction).

The patch adds a few tests for some dependency cases (that would currently produce
orphaned objects):

- schema and function (as the above scenarios)
- function and type
- table and type
---
 src/backend/catalog/dependency.c              | 48 +++++++++++++
 src/backend/catalog/objectaddress.c           | 70 +++++++++++++++++++
 src/backend/catalog/pg_depend.c               |  6 ++
 src/include/catalog/dependency.h              |  1 +
 src/include/catalog/objectaddress.h           |  1 +
 src/test/modules/Makefile                     |  1 +
 src/test/modules/meson.build                  |  1 +
 .../test_dependencies_locks/.gitignore        |  3 +
 .../modules/test_dependencies_locks/Makefile  | 14 ++++
 .../expected/test_dependencies_locks.out      | 49 +++++++++++++
 .../test_dependencies_locks/meson.build       | 12 ++++
 .../specs/test_dependencies_locks.spec        | 39 +++++++++++
 12 files changed, 245 insertions(+)
  38.1% src/backend/catalog/
  30.7% src/test/modules/test_dependencies_locks/expected/
  19.5% src/test/modules/test_dependencies_locks/specs/
   8.7% src/test/modules/test_dependencies_locks/

diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index d4b5b2ade1..e3b66025dd 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -1517,6 +1517,54 @@ AcquireDeletionLock(const ObjectAddress *object, int flags)
 	}
 }
 
+/*
+ * depLockAndCheckObject
+ *
+ * Lock the object that we are about to record a dependency on.
+ * After it's locked, verify that it hasn't been dropped while we
+ * weren't looking.  If the object has been dropped, this function
+ * does not return!
+ */
+void
+depLockAndCheckObject(const ObjectAddress *object)
+{
+	char	   *object_description;
+
+	/*
+	 * Those don't rely on LockDatabaseObject() when being dropped (see
+	 * AcquireDeletionLock()). Also it looks like they can not produce
+	 * orphaned dependent objects when being dropped.
+	 */
+	if (object->classId == RelationRelationId || object->classId == AuthMemRelationId)
+		return;
+
+	object_description = getObjectDescription(object, true);
+
+	/* assume we should lock the whole object not a sub-object */
+	LockDatabaseObject(object->classId, object->objectId, 0, AccessShareLock);
+
+	/* check if object still exists */
+	if (!ObjectByIdExist(object, false))
+	{
+		/*
+		 * It might be possible that we are creating it, so bypass the syscache
+		 * lookup and use a dirty snaphot instead.
+		 */
+		if (!ObjectByIdExist(object, true))
+		{
+			if(object_description)
+				ereport(ERROR, errmsg("%s does not exist", object_description));
+			else
+				ereport(ERROR, errmsg("a dependent object does not exist"));
+		}
+	}
+
+	if (object_description)
+		pfree(object_description);
+
+	return;
+}
+
 /*
  * ReleaseDeletionLock - release an object deletion lock
  *
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 7b536ac6fd..c2b873dd81 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -2590,6 +2590,76 @@ get_object_namespace(const ObjectAddress *address)
 	return oid;
 }
 
+/*
+ * ObjectByIdExist
+ *
+ * Return whether the given object exists.
+ *
+ * Works for most catalogs, if no special processing is needed.
+ */
+bool
+ObjectByIdExist(const ObjectAddress *address, bool use_dirty_snapshot)
+{
+	HeapTuple	tuple;
+	int			cache = -1;
+	const ObjectPropertyType *property;
+
+	if (!use_dirty_snapshot)
+	{
+		property = get_object_property_data(address->classId);
+
+		cache = property->oid_catcache_id;
+	}
+
+	if (cache >= 0)
+	{
+		/* Fetch tuple from syscache. */
+		tuple = SearchSysCache1(cache, ObjectIdGetDatum(address->objectId));
+
+		if (!HeapTupleIsValid(tuple))
+		{
+			return false;
+		}
+
+		ReleaseSysCache(tuple);
+
+		return true;
+	}
+	else
+	{
+		Relation	rel;
+		ScanKeyData skey[1];
+		SysScanDesc scan;
+		SnapshotData DirtySnapshot;
+		Snapshot	snapshot;
+
+		if (use_dirty_snapshot)
+		{
+			InitDirtySnapshot(DirtySnapshot);
+			snapshot = &DirtySnapshot;
+		}
+		else
+			snapshot = NULL;
+
+		rel = table_open(address->classId, AccessShareLock);
+
+		ScanKeyInit(&skey[0],
+					get_object_attnum_oid(address->classId),
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(address->objectId));
+
+		scan = systable_beginscan(rel, get_object_oid_index(address->classId), true,
+								  snapshot, 1, skey);
+
+		/* we expect exactly one match */
+		tuple = systable_getnext(scan);
+		systable_endscan(scan);
+		table_close(rel, AccessShareLock);
+
+		return (HeapTupleIsValid(tuple));
+	}
+}
+
 /*
  * Return ObjectType for the given object type as given by
  * getObjectTypeDescription; if no valid ObjectType code exists, but it's a
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c
index f85a898de8..6bb218f5dd 100644
--- a/src/backend/catalog/pg_depend.c
+++ b/src/backend/catalog/pg_depend.c
@@ -106,6 +106,12 @@ recordMultipleDependencies(const ObjectAddress *depender,
 		if (isObjectPinned(referenced))
 			continue;
 
+		/*
+		 * Acquire a lock and check object still exists while recording the
+		 * dependency. XXX - Should we do so only for DEPENDENCY_NORMAL?
+		 */
+		depLockAndCheckObject(referenced);
+
 		if (slot_init_count < max_slots)
 		{
 			slot[slot_stored_count] = MakeSingleTupleTableSlot(RelationGetDescr(dependDesc),
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index ec654010d4..8915548711 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -94,6 +94,7 @@ typedef struct ObjectAddresses ObjectAddresses;
 /* in dependency.c */
 
 extern void AcquireDeletionLock(const ObjectAddress *object, int flags);
+extern void depLockAndCheckObject(const ObjectAddress *object);
 
 extern void ReleaseDeletionLock(const ObjectAddress *object);
 
diff --git a/src/include/catalog/objectaddress.h b/src/include/catalog/objectaddress.h
index 3a70d80e32..04891abcc1 100644
--- a/src/include/catalog/objectaddress.h
+++ b/src/include/catalog/objectaddress.h
@@ -53,6 +53,7 @@ extern void check_object_ownership(Oid roleid,
 								   Node *object, Relation relation);
 
 extern Oid	get_object_namespace(const ObjectAddress *address);
+extern bool ObjectByIdExist(const ObjectAddress *address, bool use_dirty_snapshot);
 
 extern bool is_objectclass_supported(Oid class_id);
 extern const char *get_object_class_descr(Oid class_id);
diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index 256799f520..75f357100f 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -17,6 +17,7 @@ SUBDIRS = \
 		  test_copy_callbacks \
 		  test_custom_rmgrs \
 		  test_ddl_deparse \
+		  test_dependencies_locks \
 		  test_dsa \
 		  test_dsm_registry \
 		  test_extensions \
diff --git a/src/test/modules/meson.build b/src/test/modules/meson.build
index d8fe059d23..60305dcccd 100644
--- a/src/test/modules/meson.build
+++ b/src/test/modules/meson.build
@@ -16,6 +16,7 @@ subdir('test_bloomfilter')
 subdir('test_copy_callbacks')
 subdir('test_custom_rmgrs')
 subdir('test_ddl_deparse')
+subdir('test_dependencies_locks')
 subdir('test_dsa')
 subdir('test_dsm_registry')
 subdir('test_extensions')
diff --git a/src/test/modules/test_dependencies_locks/.gitignore b/src/test/modules/test_dependencies_locks/.gitignore
new file mode 100644
index 0000000000..bf000faac4
--- /dev/null
+++ b/src/test/modules/test_dependencies_locks/.gitignore
@@ -0,0 +1,3 @@
+# Generated subdirectories
+/log/
+/output_iso
diff --git a/src/test/modules/test_dependencies_locks/Makefile b/src/test/modules/test_dependencies_locks/Makefile
new file mode 100644
index 0000000000..7491048380
--- /dev/null
+++ b/src/test/modules/test_dependencies_locks/Makefile
@@ -0,0 +1,14 @@
+# src/test/modules/test_dependencies_locks/Makefile
+
+ISOLATION = test_dependencies_locks
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/test_dependencies_locks
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/test_dependencies_locks/expected/test_dependencies_locks.out b/src/test/modules/test_dependencies_locks/expected/test_dependencies_locks.out
new file mode 100644
index 0000000000..d0980f77d5
--- /dev/null
+++ b/src/test/modules/test_dependencies_locks/expected/test_dependencies_locks.out
@@ -0,0 +1,49 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1_begin s1_create_function_in_schema s2_drop_schema s1_commit
+step s1_begin: BEGIN;
+step s1_create_function_in_schema: CREATE FUNCTION testschema.foo() RETURNS int AS 'select 1' LANGUAGE sql;
+step s2_drop_schema: DROP SCHEMA testschema; <waiting ...>
+step s1_commit: COMMIT;
+step s2_drop_schema: <... completed>
+ERROR:  cannot drop schema testschema because other objects depend on it
+
+starting permutation: s2_begin s2_drop_schema s1_create_function_in_schema s2_commit
+step s2_begin: BEGIN;
+step s2_drop_schema: DROP SCHEMA testschema;
+step s1_create_function_in_schema: CREATE FUNCTION testschema.foo() RETURNS int AS 'select 1' LANGUAGE sql; <waiting ...>
+step s2_commit: COMMIT;
+step s1_create_function_in_schema: <... completed>
+ERROR:  schema testschema does not exist
+
+starting permutation: s1_begin s1_create_function_with_type s2_drop_foo_type s1_commit
+step s1_begin: BEGIN;
+step s1_create_function_with_type: CREATE FUNCTION footype(num foo) RETURNS int AS 'select 1' LANGUAGE sql;
+step s2_drop_foo_type: DROP TYPE public.foo; <waiting ...>
+step s1_commit: COMMIT;
+step s2_drop_foo_type: <... completed>
+ERROR:  cannot drop type foo because other objects depend on it
+
+starting permutation: s2_begin s2_drop_foo_type s1_create_function_with_type s2_commit
+step s2_begin: BEGIN;
+step s2_drop_foo_type: DROP TYPE public.foo;
+step s1_create_function_with_type: CREATE FUNCTION footype(num foo) RETURNS int AS 'select 1' LANGUAGE sql; <waiting ...>
+step s2_commit: COMMIT;
+step s1_create_function_with_type: <... completed>
+ERROR:  type foo does not exist
+
+starting permutation: s1_begin s1_create_table_with_type s2_drop_footab_type s1_commit
+step s1_begin: BEGIN;
+step s1_create_table_with_type: CREATE TABLE tabtype(a footab);
+step s2_drop_footab_type: DROP TYPE public.footab; <waiting ...>
+step s1_commit: COMMIT;
+step s2_drop_footab_type: <... completed>
+ERROR:  cannot drop type footab because other objects depend on it
+
+starting permutation: s2_begin s2_drop_footab_type s1_create_table_with_type s2_commit
+step s2_begin: BEGIN;
+step s2_drop_footab_type: DROP TYPE public.footab;
+step s1_create_table_with_type: CREATE TABLE tabtype(a footab); <waiting ...>
+step s2_commit: COMMIT;
+step s1_create_table_with_type: <... completed>
+ERROR:  type footab does not exist
diff --git a/src/test/modules/test_dependencies_locks/meson.build b/src/test/modules/test_dependencies_locks/meson.build
new file mode 100644
index 0000000000..92a978ab93
--- /dev/null
+++ b/src/test/modules/test_dependencies_locks/meson.build
@@ -0,0 +1,12 @@
+# Copyright (c) 2024, PostgreSQL Global Development Group
+
+tests += {
+  'name': 'test_dependencies_locks',
+  'sd': meson.current_source_dir(),
+  'bd': meson.current_build_dir(),
+  'isolation': {
+    'specs': [
+      'test_dependencies_locks',
+    ],
+  },
+}
diff --git a/src/test/modules/test_dependencies_locks/specs/test_dependencies_locks.spec b/src/test/modules/test_dependencies_locks/specs/test_dependencies_locks.spec
new file mode 100644
index 0000000000..fd15bd2a78
--- /dev/null
+++ b/src/test/modules/test_dependencies_locks/specs/test_dependencies_locks.spec
@@ -0,0 +1,39 @@
+setup
+{
+  CREATE SCHEMA testschema;
+  CREATE TYPE public.foo as enum ('one', 'two');
+  CREATE TYPE public.footab as enum ('three', 'four');
+}
+
+teardown
+{
+  DROP FUNCTION IF EXISTS testschema.foo();
+  DROP FUNCTION IF EXISTS footype(num foo);
+  DROP TABLE IF EXISTS tabtype;
+  DROP SCHEMA IF EXISTS testschema;
+  DROP TYPE IF EXISTS public.foo;
+  DROP TYPE IF EXISTS public.footab;
+}
+
+session "s1"
+
+step "s1_begin" { BEGIN; }
+step "s1_create_function_in_schema" { CREATE FUNCTION testschema.foo() RETURNS int AS 'select 1' LANGUAGE sql; }
+step "s1_create_function_with_type" { CREATE FUNCTION footype(num foo) RETURNS int AS 'select 1' LANGUAGE sql; }
+step "s1_create_table_with_type" { CREATE TABLE tabtype(a footab); }
+step "s1_commit" { COMMIT; }
+
+session "s2"
+
+step "s2_begin" { BEGIN; }
+step "s2_drop_schema" { DROP SCHEMA testschema; }
+step "s2_drop_foo_type" { DROP TYPE public.foo; }
+step "s2_drop_footab_type" { DROP TYPE public.footab; }
+step "s2_commit" { COMMIT; }
+
+permutation "s1_begin" "s1_create_function_in_schema" "s2_drop_schema" "s1_commit"
+permutation "s2_begin" "s2_drop_schema" "s1_create_function_in_schema" "s2_commit"
+permutation "s1_begin" "s1_create_function_with_type" "s2_drop_foo_type" "s1_commit"
+permutation "s2_begin" "s2_drop_foo_type" "s1_create_function_with_type" "s2_commit"
+permutation "s1_begin" "s1_create_table_with_type" "s2_drop_footab_type" "s1_commit"
+permutation "s2_begin" "s2_drop_footab_type" "s1_create_table_with_type" "s2_commit"
-- 
2.34.1


--Nnmp7Q5BciyL9zub--





^ permalink  raw  reply  [nested|flat] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ 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; 141+ messages in thread

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

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

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

Adding documentation 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] 141+ messages in thread


end of thread, other threads:[~2025-08-09 14:22 UTC | newest]

Thread overview: 141+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2024-03-29 15:43 [PATCH v2] Avoid orphaned objects dependencies 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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]>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox