diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index f3bf527d5b4..cbfc29c0642 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser
+
+
+ pg_stat_backend_transaction
+ pg_stat_backend_transaction
+
+
+ One row per server process, showing statistics related to
+ the current activity of that process, such as number of commits and
+ rollbacks.
+ See
+ pg_stat_backend_transaction for details.
+
+
+
pg_stat_replicationpg_stat_replicationOne row per WAL sender process, showing statistics about
@@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage
+
+ pg_stat_backend_transaction
+
+
+ pg_stat_backend_transaction
+
+
+
+ The pg_stat_backend_transaction view will have one row
+ per server process, showing statistics related to
+ the current activity of that process.
+
+
+
+ pg_stat_backend_transaction View
+
+
+
+
+ Column Type
+
+
+ Description
+
+
+
+
+
+
+
+ pidinteger
+
+
+ Process ID of this backend
+
+
+
+
+
+ xid_countbigint
+
+
+ The number of XID that have been generated by the backend. It does not take
+ into account virtual transaction ID on purpose.
+
+
+
+
+
+ xact_commitbigint
+
+
+ The number of transactions that have been committed.
+
+
+
+
+
+ xact_rollbackbigint
+
+
+ The number of transactions that have been rolled back.
+
+
+
+
+
+ stats_resettimestamp with time zone
+
+
+ Time at which these statistics were last reset
+
+
+
+
+
+
+
+
+ The view does not return statistics for the checkpointer,
+ the background writer, the startup process and the autovacuum launcher.
+
+
+
+
pg_stat_replication
@@ -4990,6 +5089,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage
+
+
+
+ pg_stat_get_backend_transactions
+
+ pg_stat_get_backend_transactions ( integer )
+ setof record
+
+
+ Returns a record of transaction statistics about the backend with the
+ specified process ID, or one record for each active backend in the system
+ if NULL is specified. The fields returned are a
+ subset of those in the pg_stat_backend_transaction view.
+
+
+
diff --git a/src/backend/access/transam/varsup.c b/src/backend/access/transam/varsup.c
index f8c4dada7c9..72c8aa7650b 100644
--- a/src/backend/access/transam/varsup.c
+++ b/src/backend/access/transam/varsup.c
@@ -24,6 +24,7 @@
#include "storage/pmsignal.h"
#include "storage/proc.h"
#include "utils/lsyscache.h"
+#include "utils/pgstat_internal.h"
#include "utils/syscache.h"
@@ -257,6 +258,9 @@ GetNewTransactionId(bool isSubXact)
/* LWLockRelease acts as barrier */
MyProc->xid = xid;
ProcGlobal->xids[MyProc->pgxactoff] = xid;
+ PendingBackendStats.pending_xid_count++;
+ backend_has_xactstats = true;
+ pgstat_report_fixed = true;
}
else
{
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index dec8df4f8ee..4d74d9719b2 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -925,6 +925,15 @@ CREATE VIEW pg_stat_activity AS
LEFT JOIN pg_database AS D ON (S.datid = D.oid)
LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
+CREATE VIEW pg_stat_backend_transaction AS
+ SELECT
+ S.pid,
+ S.xid_count,
+ S.xact_commit,
+ S.xact_rollback,
+ S.stats_reset
+ FROM pg_stat_get_backend_transactions(NULL) AS S;
+
CREATE VIEW pg_stat_replication AS
SELECT
S.pid,
diff --git a/src/backend/utils/activity/pgstat_backend.c b/src/backend/utils/activity/pgstat_backend.c
index 199ba2cc17a..dec7e5b6234 100644
--- a/src/backend/utils/activity/pgstat_backend.c
+++ b/src/backend/utils/activity/pgstat_backend.c
@@ -37,7 +37,7 @@
* reported within critical sections so we use static memory in order to avoid
* memory allocation.
*/
-static PgStat_BackendPending PendingBackendStats;
+PgStat_BackendPending PendingBackendStats;
static bool backend_has_iostats = false;
/*
@@ -48,6 +48,11 @@ static bool backend_has_iostats = false;
*/
static WalUsage prevBackendWalUsage;
+/*
+ * For backend commit and rollback statistics.
+ */
+bool backend_has_xactstats = false;
+
/*
* Utility routines to report I/O stats for backends, kept here to avoid
* exposing PendingBackendStats to the outside world.
@@ -261,6 +266,36 @@ pgstat_flush_backend_entry_wal(PgStat_EntryRef *entry_ref)
prevBackendWalUsage = pgWalUsage;
}
+/*
+ * Flush out locally pending backend transaction statistics. Locking is managed
+ * by the caller.
+ */
+static void
+pgstat_flush_backend_entry_xact(PgStat_EntryRef *entry_ref)
+{
+ PgStatShared_Backend *shbackendent;
+
+ /*
+ * This function can be called even if nothing at all has happened for
+ * transaction statistics. In this case, avoid unnecessarily modifying
+ * the stats entry.
+ */
+ if (!backend_has_xactstats)
+ return;
+
+ shbackendent = (PgStatShared_Backend *) entry_ref->shared_stats;
+
+ shbackendent->stats.xact_commit += PendingBackendStats.pending_xact_commit;
+ shbackendent->stats.xact_rollback += PendingBackendStats.pending_xact_rollback;
+ shbackendent->stats.xid_count += PendingBackendStats.pending_xid_count;
+
+ PendingBackendStats.pending_xact_commit = 0;
+ PendingBackendStats.pending_xact_rollback = 0;
+ PendingBackendStats.pending_xid_count = 0;
+
+ backend_has_xactstats = false;
+}
+
/*
* Flush out locally pending backend statistics
*
@@ -285,6 +320,10 @@ pgstat_flush_backend(bool nowait, bits32 flags)
pgstat_backend_wal_have_pending())
has_pending_data = true;
+ /* Some transaction data pending? */
+ if ((flags & PGSTAT_BACKEND_FLUSH_XACT) && backend_has_xactstats)
+ has_pending_data = true;
+
if (!has_pending_data)
return false;
@@ -300,6 +339,9 @@ pgstat_flush_backend(bool nowait, bits32 flags)
if (flags & PGSTAT_BACKEND_FLUSH_WAL)
pgstat_flush_backend_entry_wal(entry_ref);
+ if (flags & PGSTAT_BACKEND_FLUSH_XACT)
+ pgstat_flush_backend_entry_xact(entry_ref);
+
pgstat_unlock_entry(entry_ref);
return false;
diff --git a/src/backend/utils/activity/pgstat_database.c b/src/backend/utils/activity/pgstat_database.c
index b31f20d41bc..400a8c5d734 100644
--- a/src/backend/utils/activity/pgstat_database.c
+++ b/src/backend/utils/activity/pgstat_database.c
@@ -342,6 +342,13 @@ pgstat_update_dbstats(TimestampTz ts)
dbentry->blk_read_time += pgStatBlockReadTime;
dbentry->blk_write_time += pgStatBlockWriteTime;
+ /* Do the same for backend stats */
+ PendingBackendStats.pending_xact_commit += pgStatXactCommit;
+ PendingBackendStats.pending_xact_rollback += pgStatXactRollback;
+
+ backend_has_xactstats = true;
+ pgstat_report_fixed = true;
+
if (pgstat_should_report_connstat())
{
long secs;
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index a710508979e..ccc996e8b8c 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -706,6 +706,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
return (Datum) 0;
}
+/*
+ * Returns statistics of PG backends.
+ */
+Datum
+pg_stat_get_backend_transactions(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_BACKEND_STATS_COLS 5
+ int num_backends = pgstat_fetch_stat_numbackends();
+ int curr_backend;
+ int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+ InitMaterializedSRF(fcinfo, 0);
+
+ /* 1-based index */
+ for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+ {
+ /* for each row */
+ Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+ bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0};
+ LocalPgBackendStatus *local_beentry;
+ PgBackendStatus *beentry;
+ PgStat_Backend *backend_stats;
+
+ /* Get the next one in the list */
+ local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+ beentry = &local_beentry->backendStatus;
+
+ /* If looking for specific PID, ignore all the others */
+ if (pid != -1 && beentry->st_procpid != pid)
+ continue;
+
+ backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL);
+
+ values[0] = Int32GetDatum(beentry->st_procpid);
+
+ if (!backend_stats)
+ continue;
+
+ values[1] = Int64GetDatum(backend_stats->xid_count);
+ values[2] = Int64GetDatum(backend_stats->xact_commit);
+ values[3] = Int64GetDatum(backend_stats->xact_rollback);
+
+ if (backend_stats->stat_reset_timestamp != 0)
+ values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+ else
+ nulls[4] = true;
+
+ tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+
+ /* If only a single backend was requested, and we found it, break. */
+ if (pid != -1)
+ break;
+ }
+
+ return (Datum) 0;
+}
Datum
pg_backend_pid(PG_FUNCTION_ARGS)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9121a382f76..2fdfa321a3c 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5657,6 +5657,15 @@
proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
prosrc => 'pg_stat_get_activity' },
+{ oid => '9555',
+ descr => 'statistics: statistics about currently active backends',
+ proname => 'pg_stat_get_backend_transactions', prorows => '100', proisstrict => 'f',
+ proretset => 't', provolatile => 's', proparallel => 'r',
+ prorettype => 'record', proargtypes => 'int4',
+ proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}',
+ proargmodes => '{i,o,o,o,o,o}',
+ proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}',
+ prosrc => 'pg_stat_get_backend_transactions' },
{ oid => '6318', descr => 'describe wait events',
proname => 'pg_get_wait_events', procost => '10', prorows => '250',
proretset => 't', provolatile => 'v', prorettype => 'record',
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 7ae503e71a2..602ce45afb9 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -496,6 +496,9 @@ typedef struct PgStat_Backend
TimestampTz stat_reset_timestamp;
PgStat_BktypeIO io_stats;
PgStat_WalCounters wal_counters;
+ PgStat_Counter xact_commit;
+ PgStat_Counter xact_rollback;
+ PgStat_Counter xid_count;
} PgStat_Backend;
/* ---------
@@ -508,6 +511,13 @@ typedef struct PgStat_BackendPending
* Backend statistics store the same amount of IO data as PGSTAT_KIND_IO.
*/
PgStat_PendingIO pending_io;
+
+ /*
+ * Transaction statistics pending flush.
+ */
+ PgStat_Counter pending_xact_commit;
+ PgStat_Counter pending_xact_rollback;
+ PgStat_Counter pending_xid_count;
} PgStat_BackendPending;
/*
@@ -807,6 +817,13 @@ extern PGDLLIMPORT int pgstat_track_functions;
extern PGDLLIMPORT int pgstat_fetch_consistency;
+/*
+ * Variables in pgstat_backend.c
+ */
+
+extern PGDLLIMPORT PgStat_BackendPending PendingBackendStats;
+extern PGDLLIMPORT bool backend_has_xactstats;
+
/*
* Variables in pgstat_bgwriter.c
*/
diff --git a/src/include/utils/pgstat_internal.h b/src/include/utils/pgstat_internal.h
index 4d2b8aa6081..7ed4cbe4eda 100644
--- a/src/include/utils/pgstat_internal.h
+++ b/src/include/utils/pgstat_internal.h
@@ -647,7 +647,8 @@ extern void pgstat_archiver_snapshot_cb(void);
/* flags for pgstat_flush_backend() */
#define PGSTAT_BACKEND_FLUSH_IO (1 << 0) /* Flush I/O statistics */
#define PGSTAT_BACKEND_FLUSH_WAL (1 << 1) /* Flush WAL statistics */
-#define PGSTAT_BACKEND_FLUSH_ALL (PGSTAT_BACKEND_FLUSH_IO | PGSTAT_BACKEND_FLUSH_WAL)
+#define PGSTAT_BACKEND_FLUSH_XACT (1 << 2) /* Flush xact statistics */
+#define PGSTAT_BACKEND_FLUSH_ALL (PGSTAT_BACKEND_FLUSH_IO | PGSTAT_BACKEND_FLUSH_WAL | PGSTAT_BACKEND_FLUSH_XACT)
extern bool pgstat_flush_backend(bool nowait, bits32 flags);
extern bool pgstat_backend_flush_cb(bool nowait);
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 77e25ca029e..e2d87415cc7 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1857,6 +1857,12 @@ pg_stat_archiver| SELECT archived_count,
last_failed_time,
stats_reset
FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset);
+pg_stat_backend_transaction| SELECT pid,
+ xid_count,
+ xact_commit,
+ xact_rollback,
+ stats_reset
+ FROM pg_stat_get_backend_transactions(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset);
pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean,
pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean,
pg_stat_get_buf_alloc() AS buffers_alloc,
diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out
index 67e1860e984..ca599f98643 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
INSERT INTO trunc_stats_test1 DEFAULT VALUES;
UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+ FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
BEGIN;
UPDATE trunc_stats_test1 SET id = id + 100;
TRUNCATE trunc_stats_test1;
INSERT INTO trunc_stats_test1 DEFAULT VALUES;
COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush
+--------------------------
+
+(1 row)
+
+SELECT xact_commit AS xact_commit_after
+ FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+SELECT :xact_commit_after > :xact_commit_before;
+ ?column?
+----------
+ t
+(1 row)
+
-- use a savepoint: 1 insert, 1 live
BEGIN;
INSERT INTO trunc_stats_test2 DEFAULT VALUES;
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 8768e0f27fd..558b7bfb972 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES;
UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
DELETE FROM trunc_stats_test1 WHERE id = 3;
+-- in passing, check that backend's commit is incrementing
+SELECT xact_commit AS xact_commit_before
+ FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
BEGIN;
UPDATE trunc_stats_test1 SET id = id + 100;
TRUNCATE trunc_stats_test1;
INSERT INTO trunc_stats_test1 DEFAULT VALUES;
COMMIT;
+SELECT pg_stat_force_next_flush();
+SELECT xact_commit AS xact_commit_after
+ FROM pg_stat_backend_transaction WHERE pid = pg_backend_pid() \gset
+
+SELECT :xact_commit_after > :xact_commit_before;
+
-- use a savepoint: 1 insert, 1 live
BEGIN;
INSERT INTO trunc_stats_test2 DEFAULT VALUES;